
package com.hlkj.pay.app.common.impl;
/*
 * Hlpay-Plus aggregate payment system.
 * Copyright (c) 2024-2025 Hlpay Team Copyright has the right of final interpretation.
 */

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.hlkj.framework.common.pojo.CommonResult;
import com.hlkj.framework.common.util.collection.CollectionUtils;
import com.hlkj.framework.common.util.json.JsonUtils;
import com.hlkj.pay.app.common.IDownloadExcelService;
import com.hlkj.pay.app.common.IUploadService;
import com.hlkj.pay.dto.merchant.MerchantInfoQueryDto;
import com.hlkj.pay.dto.order.*;
import com.hlkj.pay.enums.CommonEnum;
import com.hlkj.pay.infrastructure.model.common.ExportRecordDO;
import com.hlkj.pay.infrastructure.model.merchant.MerchantInfoDO;
import com.hlkj.pay.infrastructure.model.order.*;
import com.hlkj.pay.infrastructure.model.pay.PayChannelDO;
import com.hlkj.pay.infrastructure.model.pay.ProviderInfoDO;
import com.hlkj.pay.service.common.IExportRecordService;
import com.hlkj.pay.service.merchant.IMerchantService;
import com.hlkj.pay.service.order.*;
import com.hlkj.pay.service.pay.IPayChannelService;
import com.hlkj.pay.service.pay.IProviderService;
import com.hlkj.pay.vo.order.resp.export.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.io.ByteArrayOutputStream;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author HlpayTeam
 * @date 2024/09/13 15:19
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class DownloadExcelServiceImpl implements IDownloadExcelService {

    private final IUploadService uploadService;
    private final IMerchantService merchantService;
    private final IRefundOrderService refundOrderService;
    private final IExportRecordService exportRecordService;
    private final IOrderStatisticsService orderStatisticsService;
    private final IPayOrderService payOrderService;
    private final IOrderComplaintService orderComplaintService;
    private final IProviderService providerService;
    private final IPayChannelService payChannelService;
    private final IOrderRiskService orderRiskService;
    private final IWithdrawOrderService withdrawOrderService;


    @Override
    public <T> void createExcel(String filename, Object paramObj, Class<T> cls) {
        ExportRecordDO exportRecordDO = new ExportRecordDO();
//        exportRecordDO.setCreatedBy(createBy);
        exportRecordDO.setExportFileName(filename);
        exportRecordDO.setParams(JSONUtil.toJsonStr(JSONUtil.parseObj(paramObj, true)));
        try {
            exportRecordDO.setStatus(1);
            exportRecordService.add(exportRecordDO);
            this.doCreateExcel(filename, paramObj, cls, exportRecordDO.getId());
        } catch (Exception e) {
            exportRecordDO.setStatus(0);
            exportRecordDO.setRemark(e.getMessage());
            exportRecordService.add(exportRecordDO);
        }
    }

    public <T> void doCreateExcel(String filename, Object paramObj, Class<T> cls, Long exportRecordId) {
        log.info("create {} excel:{}", cls.getName(), JsonUtils.toJsonString(paramObj));
        ExportRecordDO exportRecordDO = new ExportRecordDO();
        exportRecordDO.setId(exportRecordId);
        exportRecordDO.setExportFileName(filename);
        exportRecordDO.setParams(JSONUtil.toJsonStr(JSONUtil.parseObj(paramObj, true)));
        try {
            if (cls.equals(PayOrderFileExport.class)) {
                this.doTask(filename, PayOrderFileExport.class, this.orders(paramObj), exportRecordDO);
            } else if (cls.equals(MerchantInfoFileExport.class)) {
                this.doTask(filename, MerchantInfoFileExport.class, this.mechants(paramObj), exportRecordDO);
            } else if (cls.equals(RefundOrderFileExport.class)) {
                this.doTask(filename, RefundOrderFileExport.class, this.refunds(paramObj), exportRecordDO);
            } else if (cls.equals(StatisticsOrderFileExport.class)) {
                this.doTask(filename, StatisticsOrderFileExport.class, this.statistics(paramObj), exportRecordDO);
            } else if (cls.equals(StatisticsPayTypeFileExport.class)) {
                this.doTask(filename, StatisticsPayTypeFileExport.class, this.payTypeStatistics(paramObj), exportRecordDO);
            } else if (cls.equals(StatisticsChannelFileExport.class)) {
                this.doTask(filename, StatisticsChannelFileExport.class, this.channelStatistics(paramObj), exportRecordDO);
            } else if (cls.equals(StatisticsMechantFileExport.class)) {
                this.doTask(filename, StatisticsMechantFileExport.class, this.mechantStatistics(paramObj), exportRecordDO);
            } else if (cls.equals(StatisticsAgentFileExport.class)) {
                this.doTask(filename, StatisticsAgentFileExport.class, this.agentStatistics(paramObj), exportRecordDO);
            } else if (cls.equals(StatisticsAppFileExport.class)) {
                this.doTask(filename, StatisticsAppFileExport.class, this.appStatistics(paramObj), exportRecordDO);
            } else if (cls.equals(OrderComplaintExport.class)) {
                this.doTask(filename, OrderComplaintExport.class, this.complaints(paramObj), exportRecordDO);
            } else if (cls.equals(StatisticsPayChannelFileExport.class)) {
                this.doTask(filename, StatisticsPayChannelFileExport.class, this.payChannelStatistics(paramObj), exportRecordDO);
            } else if (cls.equals(StatisticsPayAutCodeFileExport.class)) {
                this.doTask(filename, StatisticsPayAutCodeFileExport.class, this.payAutCodeStatistics(paramObj), exportRecordDO);
            } else if (cls.equals(OrderRiskExport.class)) {
                this.doTask(filename, OrderRiskExport.class, this.risks(paramObj), exportRecordDO);
            } else if (cls.equals(WithdrawOrderFileExport.class)) {
                this.doTask(filename, WithdrawOrderFileExport.class, this.withDrawOrders(paramObj), exportRecordDO);
            }
        } catch (Exception e) {
            exportRecordDO.setStatus(0);
            exportRecordDO.setRemark(e.getMessage());
            exportRecordService.add(exportRecordDO);
        }
    }

    <T> void doTask(String filename, Class<T> cls, List<T> data, ExportRecordDO exportRecordDO) {
        try {
            if (CollectionUtils.isNotEmpty(data)) {
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                EasyExcel.write(outputStream, cls)
                        .registerWriteHandler(getHorizontalCellStyleStrategy())
                        .sheet(filename)
                        .doWrite(data);
                CommonResult<String> commonResult = uploadService.upload(outputStream.toByteArray(), CommonEnum.OssEnum.EXCEL);
                if (commonResult.isSuccess()) {
                    exportRecordDO.setStatus(2);
                    exportRecordDO.setExportFileUrl(commonResult.getData());
                } else {
                    exportRecordDO.setStatus(0);
                    exportRecordDO.setRemark(commonResult.getMsg());
                }
            } else {
                exportRecordDO.setStatus(2);
                exportRecordDO.setRemark("导出数据为空");
            }
        } catch (Exception e) {
            exportRecordDO.setStatus(0);
            exportRecordDO.setRemark(e.getMessage());
        }
        exportRecordService.update(exportRecordDO);
    }

    List<OrderComplaintExport> complaints(Object paramObj) {
        List<OrderComplaintDO> list = orderComplaintService.queryList((ComplaintOrderQueryDto)paramObj);
        for(OrderComplaintDO orderComplaintDO:list){
            if(StrUtil.isNotEmpty(orderComplaintDO.getChannelCode())){
                PayChannelDO payChannelDO = payChannelService.queryByCode(orderComplaintDO.getChannelCode());
                if(payChannelDO != null){
                    orderComplaintDO.setChannelName(payChannelDO.getName());
                }
            }
            if(StrUtil.isNotEmpty(orderComplaintDO.getProviderCode())){
                ProviderInfoDO providerInfoDO = providerService.queryByCode(orderComplaintDO.getProviderCode());
                if(providerInfoDO != null){
                    orderComplaintDO.setProviderName(providerInfoDO.getShortName());
                }
            }
            if(StrUtil.isNotEmpty(orderComplaintDO.getSn())){
                MerchantInfoDO merchantInfoDO = merchantService.queryMerchant(orderComplaintDO.getSn());
                if(merchantInfoDO != null){
                    orderComplaintDO.setSnName(merchantInfoDO.getName());
                }
            }
        }

        return cn.hutool.core.bean.BeanUtil.copyToList(list, OrderComplaintExport.class);
    }

    List<OrderRiskExport> risks(Object paramObj) {
        List<OrderRiskDO> list = orderRiskService.queryList((RiskOrderQueryDto)paramObj);
        for(OrderRiskDO orderRiskDO:list){
            if(StrUtil.isNotEmpty(orderRiskDO.getChannelCode())){
                PayChannelDO payChannelDO = payChannelService.queryByCode(orderRiskDO.getChannelCode());
                if(payChannelDO != null){
                    orderRiskDO.setChannelName(payChannelDO.getName());
                }
            }
            if(StrUtil.isNotEmpty(orderRiskDO.getProviderCode())){
                ProviderInfoDO providerInfoDO = providerService.queryByCode(orderRiskDO.getProviderCode());
                if(providerInfoDO != null){
                    orderRiskDO.setProviderName(providerInfoDO.getShortName());
                }
            }
            if(StrUtil.isNotEmpty(orderRiskDO.getSn())){
                MerchantInfoDO merchantInfoDO = merchantService.queryMerchant(orderRiskDO.getSn());
                if(merchantInfoDO != null){
                    orderRiskDO.setSnName(merchantInfoDO.getName());
                }
            }
        }
        return cn.hutool.core.bean.BeanUtil.copyToList(list, OrderRiskExport.class);
    }

    List<PayOrderFileExport> orders(Object paramObj) {
        List<PayOrderDO> list = payOrderService.queryList((PayOrderQueryDto)paramObj);
        return cn.hutool.core.bean.BeanUtil.copyToList(list, PayOrderFileExport.class);
    }

    List<MerchantInfoFileExport> mechants(Object paramObj) {
        List<MerchantInfoDO> list = merchantService.queryList((MerchantInfoQueryDto)paramObj);
        return cn.hutool.core.bean.BeanUtil.copyToList(list, MerchantInfoFileExport.class);
    }

    List<RefundOrderFileExport> refunds(Object paramObj) {
        List<RefundOrderDO> list = refundOrderService.queryList((RefundOrderQueryDto)paramObj);
        return cn.hutool.core.bean.BeanUtil.copyToList(list, RefundOrderFileExport.class);
    }

    List<StatisticsOrderFileExport> statistics(Object paramObj) {
        OrderStatisticsQueryDto orderStatisticsQueryDto = (OrderStatisticsQueryDto)paramObj;
        return cn.hutool.core.bean.BeanUtil.copyToList(orderStatisticsService.queryTranList(orderStatisticsQueryDto), StatisticsOrderFileExport.class);
    }

    List<StatisticsPayTypeFileExport> payTypeStatistics(Object paramObj) {
        return cn.hutool.core.bean.BeanUtil.copyToList(orderStatisticsService.queryCommonList((OrderStatisticsQueryDto)paramObj), StatisticsPayTypeFileExport.class);
    }

    List<StatisticsMechantFileExport> mechantStatistics(Object paramObj) {
        List<StatisticsMechantFileExport> data = cn.hutool.core.bean.BeanUtil.copyToList(orderStatisticsService.queryCommonList((OrderStatisticsQueryDto)paramObj), StatisticsMechantFileExport.class);
        List<String> snList = data.stream().map(roleResp -> roleResp.getSn()).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(snList)) {
            List<MerchantInfoDO> merchantInfoList = merchantService.queryList(snList);
            Map<String, MerchantInfoDO> merchantInfoMap = merchantInfoList.stream()
                    .collect(Collectors.toMap(merchantInfoDO -> merchantInfoDO.getSn(), Function.identity(), (t1, t2) -> t1));
            data.stream().forEach(roleResp -> {
                MerchantInfoDO merchantInfoDO = merchantInfoMap.get(roleResp.getSn());
                if (merchantInfoDO != null) {
                    roleResp.setMerchantName(merchantInfoDO.getName());
                }
            });
        }
        return data;
    }

    List<StatisticsAgentFileExport> agentStatistics(Object paramObj) {
        List<StatisticsAgentFileExport> data = cn.hutool.core.bean.BeanUtil.copyToList(orderStatisticsService.queryAgentList((OrderStatisticsQueryDto)paramObj), StatisticsAgentFileExport.class);
        List<String> snList = data.stream().map(roleResp -> roleResp.getSn()).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(snList)) {
            List<MerchantInfoDO> merchantInfoList = merchantService.queryList(snList);
            Map<String, MerchantInfoDO> merchantInfoMap = merchantInfoList.stream()
                    .collect(Collectors.toMap(merchantInfoDO -> merchantInfoDO.getSn(), Function.identity(), (t1, t2) -> t1));
            data.stream().forEach(roleResp -> {
                MerchantInfoDO merchantInfoDO = merchantInfoMap.get(roleResp.getSn());
                if (merchantInfoDO != null) {
                    roleResp.setAgentName(merchantInfoDO.getName());
                }
            });
        }
        return data;
    }

    List<StatisticsAppFileExport> appStatistics(Object paramObj) {
        List<StatisticsAppFileExport> data = cn.hutool.core.bean.BeanUtil.copyToList(orderStatisticsService.queryCommonList((OrderStatisticsQueryDto)paramObj), StatisticsAppFileExport.class);
        List<String> snList = data.stream().map(roleResp -> roleResp.getSn()).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(snList)) {
            List<MerchantInfoDO> merchantInfoList = merchantService.queryList(snList);
            Map<String, MerchantInfoDO> merchantInfoMap = merchantInfoList.stream()
                    .collect(Collectors.toMap(merchantInfoDO -> merchantInfoDO.getSn(), Function.identity(), (t1, t2) -> t1));
            data.stream().forEach(roleResp -> {
                MerchantInfoDO merchantInfoDO = merchantInfoMap.get(roleResp.getSn());
                if (merchantInfoDO != null) {
                    roleResp.setMerchantName(merchantInfoDO.getName());
                }
            });
        }
        return data;
    }

    List<StatisticsChannelFileExport> channelStatistics(Object paramObj) {
        List<StatisticsChannelFileExport> data = cn.hutool.core.bean.BeanUtil.copyToList(orderStatisticsService.queryMchChannelCodeList((OrderStatisticsQueryDto)paramObj), StatisticsChannelFileExport.class);
        List<String> snList = data.stream().map(roleResp -> roleResp.getSn()).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(snList)) {
            List<MerchantInfoDO> merchantInfoList = merchantService.queryList(snList);
            Map<String, MerchantInfoDO> merchantInfoMap = merchantInfoList.stream()
                    .collect(Collectors.toMap(merchantInfoDO -> merchantInfoDO.getSn(), Function.identity(), (t1, t2) -> t1));
            data.stream().forEach(roleResp -> {
                MerchantInfoDO merchantInfoDO = merchantInfoMap.get(roleResp.getSn());
                if (merchantInfoDO != null) {
                    roleResp.setMerchantName(merchantInfoDO.getName());
                }
            });
        }
        return data;
    }

    List<StatisticsPayChannelFileExport> payChannelStatistics(Object paramObj) {
        return cn.hutool.core.bean.BeanUtil.copyToList(orderStatisticsService.queryChannelCodeList((OrderStatisticsQueryDto)paramObj), StatisticsPayChannelFileExport.class);
    }

    List<StatisticsPayAutCodeFileExport> payAutCodeStatistics(Object paramObj) {
        return cn.hutool.core.bean.BeanUtil.copyToList(orderStatisticsService.queryAutCodeList((OrderStatisticsQueryDto)paramObj), StatisticsPayAutCodeFileExport.class);
    }


    List<WithdrawOrderFileExport> withDrawOrders(Object paramObj) {
        List<OrderWithdrawDto> list = withdrawOrderService.queryPageList((WithdrawOrderQueryDto)paramObj);
        return cn.hutool.core.bean.BeanUtil.copyToList(list, WithdrawOrderFileExport.class);
    }


    public static HorizontalCellStyleStrategy getHorizontalCellStyleStrategy() {
        // 头的策略
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        // 背景设置为白色
        headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.index);
        headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        headWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);

        //边框
        headWriteCellStyle.setBorderBottom(BorderStyle.THIN);
        headWriteCellStyle.setBorderLeft(BorderStyle.THIN);
        headWriteCellStyle.setBorderRight(BorderStyle.THIN);
        headWriteCellStyle.setBorderTop(BorderStyle.THIN);
        //自动换行
        headWriteCellStyle.setWrapped(true);
        WriteFont headWriteFont = new WriteFont();
        headWriteFont.setBold(true);
        headWriteFont.setFontName("宋体");
        headWriteFont.setFontHeightInPoints((short)12);
        headWriteCellStyle.setWriteFont(headWriteFont);
        // 内容的策略
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定
        contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
        // 背景白色
        contentWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        //边框
        contentWriteCellStyle.setBorderBottom(BorderStyle.THIN);
        contentWriteCellStyle.setBorderLeft(BorderStyle.THIN);
        contentWriteCellStyle.setBorderRight(BorderStyle.THIN);
        contentWriteCellStyle.setBorderTop(BorderStyle.THIN);
        //自动换行
        contentWriteCellStyle.setWrapped(true);
        //文字
        WriteFont contentWriteFont = new WriteFont();
        // 字体大小
        contentWriteFont.setFontHeightInPoints((short)12);
        contentWriteFont.setFontName("宋体");
        contentWriteCellStyle.setWriteFont(contentWriteFont);
        // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现
        return new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
    }

}
